/**
 * File:		XMLHandler.java
 * Project:		Virtual Advisor
 * Module:		Data Management
 * Author:		Rotem Ganel
 * Created:		April 12, 2011
 * Updated:		April 30, 2011
 * Version:		1 (in progress)
 */
package server.dataManagement;

import server.dataManagement.Queries.AdminQuery;
import server.dataManagement.Queries.CourseQuery;
import server.dataManagement.Queries.Query;
import server.dataManagement.Queries.StudentQuery;
import server.dataManagement.exceptions.RepositoryFilenamesNotSetException;

import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;
import java.io.RandomAccessFile;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;


public abstract class XMLHandler {
	//TODO: error check setFilenames
	//TODO: filename invalid (file could not be found or opened) exception
	private static String[] filenames;
	public static enum FILE_INDEX {CATALOG(0), CURRICULUM(1), USERS(2), INVALID(-1);
		private final int index;
	
		private FILE_INDEX(int index) {
			this.index = index;
		}
	};
		
	/**
	 * Name:			
	 * Description:		
	 * PreCondition:	
	 * PostCondition:	
	 * Parameters:		
	 * Notes:			
	 */
	public static void setFilenames(String catalogFile, String curriculumFile, String userFile) {
		filenames = new String[]{catalogFile, curriculumFile, userFile};
	}
	
	public static String[] getFilenames() throws RepositoryFilenamesNotSetException {
		if (filenames == null) {
			throw new RepositoryFilenamesNotSetException();
		}
		
		String[] names = new String[filenames.length];
		
		for (int i = 0; i < filenames.length; i++) {
			names[i] = names[i];
		}
		
		return names;
	}
	
	public static boolean filenamesSet() {
		return (filenames != null);
	}
	
	private static int FILE(String queryName) {
		if (queryName.equals("class server.dataManagement.Queries.CourseQuery")) {
			return FILE_INDEX.CATALOG.index;
		} else if (queryName.equals("class server.dataManagement.Queries.StudentQuery")) {
			return FILE_INDEX.USERS.index;
		} else if (queryName.equals("class server.dataManagement.Queries.PlanQuery")) {
			return FILE_INDEX.USERS.index;
		} else if (queryName.equals("class server.dataManagement.Queries.SemesterQuery")) {
			return FILE_INDEX.USERS.index;
		} else if (queryName.equals("class server.dataManagement.Queries.RecordQuery")) {
			return FILE_INDEX.USERS.index;
		} else if (queryName.equals("class server.dataManagement.Queries.UserTypeQuery")) {
			return FILE_INDEX.USERS.index;
		} else if (queryName.equals("class server.dataManagement.Queries.AdminQuery")) {
			return FILE_INDEX.USERS.index;
		} else if (queryName.equals("class server.dataManagement.Queries.DegreeQuery")) {
			return FILE_INDEX.CURRICULUM.index;
		} else if (queryName.equals("class server.dataManagement.Queries.SectionQuery")) {
			return FILE_INDEX.CURRICULUM.index;
		} else if (queryName.equals("class server.dataManagement.Queries.SubsectionQuery")) {
			return FILE_INDEX.CURRICULUM.index;
		} else if (queryName.equals("class server.dataManagement.Queries.SeriesQuery")) {
			return FILE_INDEX.CURRICULUM.index;
		} else if (queryName.equals("class server.dataManagement.Queries.ReqCourseQuery")) {
			return FILE_INDEX.CURRICULUM.index;
		} else {
//			System.out.println(queryName);
			return FILE_INDEX.INVALID.index;
		}
	}
		
	/**
	 * Name:			
	 * Description:		
	 * PreCondition:	
	 * PostCondition:	
	 * Parameters:		
	 * Notes:			
	 */
	public static boolean query(Query q) throws IOException {
		//TODO: handle exceptions: IO, IndexOutOfBounds, + project specific
		//TODO: split into helper methods
		//TODO:	Use RandomAcessFile
		
		int repositoryIndex = FILE(q.QUERY_TYPE());
		String searchFlag = q.searchFlag;
		String[][] flags = q.flags();
						
		//TODO: derp. queries are for reading.
		//Check action type
		switch (q.action) {
		case Create:
				if (!q.fulfilled()) {
					return false;
				} else {
					return createInstance(filenames[repositoryIndex], q.review(), searchFlag, flags);
				}
		case Read:
				if (q.fulfilled()) {
					return false;
				} else {
					return q.respond(findInstance(filenames[repositoryIndex], searchFlag, flags));			
				}
		case Update: break;
		case Delete: break;
		default:
			return false;
		}
		
		return false;
	}
	
	public static boolean query(Query.ACTION action, ArrayList<Query> subqueries) throws IOException {
		switch (action) {
		case Create:
//			System.out.println(prepareInstance(subqueries));
//			return createInstance(subqueries);
			break;
		case Read:
			for (Query q : subqueries) {
				if (q.fulfilled()) {
					return false;
				} else {
					if (!query(q)) {
						return false;			
					}
				}
			}
			
			return true;
		case Update: break;
		case Delete: break;
		default:
			return false;
		}
		
		return false;
	}
		
	public static String[] InventoryCatalog() throws IOException, RepositoryFilenamesNotSetException {
		//TODO: write checkFilenamesSet() to throw exception if needed
		if (filenames == null) {
			throw new RepositoryFilenamesNotSetException();
		}
		
		return simpleInventory(filenames[FILE_INDEX.CATALOG.index], CourseQuery.FIELD.ID.flags());
	}
	
	public static String[] InventoryUsernames() throws RepositoryFilenamesNotSetException, IOException {
		if (filenames == null) {
			throw new RepositoryFilenamesNotSetException();
		}
		
		String[] admins = simpleInventory(filenames[FILE_INDEX.USERS.index], AdminQuery.FIELD.Username.flags());
		String[] students = simpleInventory(filenames[FILE_INDEX.USERS.index], StudentQuery.FIELD.Username.flags());
		
		String[] results = new String[admins.length + students.length];
		for (int i = 0; i < admins.length; i++) {
			results[i] = admins[i];
		}
		
		for (int i = admins.length; i < results.length; i++)	{
			results[i] = students[i - admins.length];
		}
		
		return results;
	}
	
	public static void hax(String write, String filename) throws IOException {
		FileWriter fWriter = new FileWriter(filename);
		BufferedWriter bWriter = new BufferedWriter(fWriter);
		
		bWriter.write(write);
		
		bWriter.flush();
		fWriter.flush();
		bWriter.close();
		fWriter.close();
	}
	
	private static boolean createInstance(String filename, String[] fields, String firstField, String[][] flags) throws IOException {
		filename = "derpderderp.xml";
		String entry = prepareInstance(fields, firstField, flags);
		
		RandomAccessFile kingOfTheFiles = new RandomAccessFile(filename, "rwd");
		
		//From the beginning...
		kingOfTheFiles.seek(0);
		
		//Find where it goes
		int endFirstFlag = firstField.indexOf("\"") + 1;
		int beginEndFlag = firstField.indexOf("\"", endFirstFlag);
		String firstFlag = firstField.substring(0, endFirstFlag);
		String searchField = firstField.substring(endFirstFlag, beginEndFlag);
		String endFlag = firstField.substring(beginEndFlag);

		String line;
		int comparison;
		while ((line = kingOfTheFiles.readLine()) != null) {
			if (line.indexOf(firstFlag) != -1) {
				//find alphanumeric ordering
				//TODO: STUB
			}
		}
		
		//Make sure it's not already there
		
		//Save location
		long pos = kingOfTheFiles.getFilePointer();
		
		//Shift everything over
		byte[] content = new byte[(int)(kingOfTheFiles.length() - pos)];
		kingOfTheFiles.setLength(kingOfTheFiles.length() + entry.length());
		kingOfTheFiles.readFully(content);
		kingOfTheFiles.seek(pos + entry.length());
		kingOfTheFiles.write(content);
		
		//Write it where it goes
		kingOfTheFiles.seek(pos);
		kingOfTheFiles.writeBytes(entry);
		
		//Change updated tag
		updateDate(kingOfTheFiles);
		
		//Close file thinger
		kingOfTheFiles.close();
		
		return true;
	}
	
	private static void updateDate(RandomAccessFile fileThinger) throws IOException {
		//Grab position
		long pos = fileThinger.getFilePointer();
		
		//Go back, back, back to the beginning
		fileThinger.seek(0);
		
		//Find the position of the date
		String line = fileThinger.readLine();
		int index = line.indexOf("updated=\"") + 9;
		
		//Get the date 0.o
		Date today = new Date();
		String formattedDate = "MM/dd/yy";
		SimpleDateFormat formatter = new SimpleDateFormat(formattedDate);
		formattedDate = formatter.format(today);
		
		//Set position againnn
		fileThinger.seek(index);
		
		//Write date
		fileThinger.writeBytes(formattedDate);
		
		//Point back to wherever it was before
		fileThinger.seek(pos);
	}
	
	private static String prepareInstance(String[] fields, String firstField, String[][]flags) {	
		//First field
		String tab = "\t";
		String instance = tab + firstField + "\n";
		
		tab += "\t";
		for (int i = 0; i < flags.length; i++) {			
			if (flags[i].length == 2) {
				//simple field
				instance += tab + flags[i][0] + fields[i + 1] + flags[i][1] + "\n";
			} else {
				//parent tag
				instance += tab + flags[i][0] + "\n";

				String[] subfields = fields[i + 1].split(", ");
				for (String subfield : subfields) {
					if (!subfield.equals("")) {
						//If child found, add it
						instance += tab + "\t" + flags[i][1] + subfield + flags[i][2] + "\n";
					}
				}
				
				//end parent tag
				instance += tab + flags[i][3] + "\n";
			}
		}
		
		//first field's close tag
		int endIndex = firstField.indexOf(" ");
		instance += "\t</" + firstField.substring(1, endIndex) + ">\n";
		
		return instance;
	}
		
	public static String prepareInstance(ArrayList<Query> subqueries) {
		ArrayList<String> fields = collateQueryFields(subqueries);
		ArrayList<String[]> flags = collateQueryFlags(subqueries);
		
		String instance = "";
		
		int i = 0;
		for (String[] sequence : flags) {
			if (sequence.length == 1) {
//				System.out.println("len 1 " + sequence[0]);
				instance += sequence[0];
			} else {
				if (fields.get(i).indexOf(", ") == -1) {
//					System.out.println("len 2 " + sequence[0] + fields.get(i) + sequence[1]);
					instance += sequence[0] + fields.get(i) + sequence[1];
					i++;
				} else {
					String[] children = fields.get(i).split(", ");
					for (String child : children) {
//						System.out.println("child " + sequence[0] + child + sequence[1]);
						instance += sequence[0] + child + sequence[1];
					}
					i++;
				}
			}
			
			instance += "\n";
		}
		
		return instance;
	}
	
	//TODO: untested
	public static ArrayList<String> collateQueryFields(ArrayList<Query> subqueries) {
		ArrayList<String> fields = new ArrayList<String>();
		
		for (Query q : subqueries) {
			if (q.fulfilled()) {
				for (String field : q.review()) {
					fields.add(field);
				}
			}
		}
		
		//Get rid of duplicate first field
		fields.remove(0);
		
		return fields;
	}
	
	//TODO: doesn't *quite* work for degree query sequence (curriculum)
	public static ArrayList<String[]> collateQueryFlags(ArrayList<Query> subqueries) {
		ArrayList<String[]> flags = new ArrayList<String[]>();
		ArrayList<String> endFlagStack = new ArrayList<String>();

		//First entry...
		String firstField = subqueries.get(0).searchFlag;
		flags.add(new String[]{firstField});
		int endIndex = firstField.indexOf(" ");
//		System.out.println("\nAdd to stack: " + "</" + firstField.substring(1, endIndex) + ">\n");
		endFlagStack.add("</" + firstField.substring(1, endIndex) + ">"); 
		
		//Subsequent entries...
		for (Query q : subqueries) {
			if ((q.QUERY_TYPE().equals("class server.dataManagement.SemesterQuery")) ||
					(q.QUERY_TYPE().equals("class server.dataManagement.RecordQuery"))) {
				flags.add(new String[]{q.searchFlag});
				endIndex = q.searchFlag.indexOf(" ");
//				System.out.println("\nAdd to stack: " + "</" + q.searchFlag.substring(1, endIndex) + ">\n");
				endFlagStack.add("</" + q.searchFlag.substring(1, endIndex) + ">"); 
			}
			
			String[][] localFlags = q.flags();
			for (String[] sequence : localFlags) {
				if (sequence.length == 2) {
					flags.add(sequence);
					
					if ((sequence[0].endsWith("\"")) && (!sequence[1].contains("\" />"))) {
						endIndex = sequence[0].indexOf(" ");
//						System.out.println("\nAdd to stack: " + "</" + sequence[0].substring(1, endIndex) + ">\n");
						endFlagStack.add("</" + sequence[0].substring(1, endIndex) + ">");
					}
				} else {
					String beginFlag = sequence[0];
					if (beginFlag.endsWith("\"")) {
						flags.add(new String[]{sequence[0], "\" />"});
					} else {
						flags.add(new String[]{sequence[0]});
					}

					endFlagStack.add(sequence[3]);
				}
			}
		}
		
		for (int i = endFlagStack.size() - 1; i >= 0; i--) {
			flags.add(new String[]{endFlagStack.get(i)});
			endFlagStack.remove(i);
		}
		
/*		for (String[] s : flags) {
			for (String f : s) {
				System.out.print(f + " ");
			}
			System.out.println();
		}*/
		
		return flags;
	}
	
	private static String[] findInstance(String filename, String searchFlag, String[][] flags) throws IOException {
		String[] instance = new String[flags.length];
		
		for (int i = 0; i < flags.length; i++) {
			if (flags[i].length == 2) {
				instance[i] = findSimpleField(filename, searchFlag, flags[i]);
			} else if (flags[i].length == 4) {
				instance[i] = findComplexField(filename, searchFlag, flags[i]);
			} else {
				//TODO: throw exception?
				return null;
			}
		}
		
		return instance;
	}
	
	//Returns first occurrence
	private static String findSimpleField(String filename, String queue, String[] flags) throws IOException {
		if (flags.length == 4) {
			return findComplexField(filename, queue, flags) ;
		}
		
		//TODO: field not found exception
		String result = null;
		boolean queued = false;
		
		FileReader fReader = new FileReader(filename);
        BufferedReader bReader = new BufferedReader (fReader);
        
        String line = null;
        int startIndex, endIndex;
        while ((line = bReader.readLine()) != null)
        {
        	if (line.indexOf(queue) != -1) {
        		queued = true;
        	}
        	
        	if (queued) {
            	startIndex = line.indexOf(flags[0]);
            	
            	if (startIndex != -1) {
            		startIndex += flags[0].length();
            		endIndex = line.indexOf(flags[1], startIndex);
            		result = line.substring(startIndex, endIndex);
            		break;
            	}

        	}
        }
        
        bReader.close();
        fReader.close();
        
        return result;
	}
	
	//Returns first occurrence
	private static String findComplexField(String filename, String queue, String[] flags) throws IOException {
		if (flags.length == 2) {
			return findSimpleField(filename, queue, flags) ;
		}
		
        ArrayList<String> series = new ArrayList<String>();
        String result = "";
        boolean queued = false;
        
		FileReader fReader = new FileReader(filename);
        BufferedReader bReader = new BufferedReader (fReader);
        
        String line = null;
        int startIndex, subStartIndex, subEndIndex;
        while ((line = bReader.readLine()) != null)
        {
        	//Queue search up to relevant entry
           	if (line.indexOf(queue) != -1) {
           		queued = true;
           	}
            	
           	if (queued) {
           		//Find the first start tag
        		startIndex = line.indexOf(flags[0]);
            	
            	if (startIndex != -1) {
            		line = bReader.readLine();
            		
            		//Loop through the children
            		while ((line.indexOf(flags[3]) == -1)) {
            			//Search for child
            			subStartIndex = line.indexOf(flags[1]);

            			//If child is found
            			if (subStartIndex != -1) {            				
                			//Set indexes for extracting the child
            				subStartIndex += flags[1].length();
            				subEndIndex = line.indexOf(flags[2], subStartIndex);
            				
            				//Add child to series
            				series.add(line.substring(subStartIndex, subEndIndex));
            			}
            			
            			//Load next line
            			line = bReader.readLine();
            		}
            		
            		if (series.size() == 0) {
            			break;
            		}
            		
        			for (String s : series) {
        				result += s + ", ";
        			}
        			
        			result = result.substring(0, result.length() - 2);
        			break;
            	}
        	}
        }
        
        bReader.close();
        fReader.close();
        
		return result;
	}
	
	/**
	 * Name:			
	 * Description:		
	 * PreCondition:	
	 * PostCondition:	
	 * Parameters:		
	 * Notes:			
	 */
	private static String[] simpleInventory(String filename, String[] flags) throws IOException {
		if (flags.length == 4) {
			return complexInventory(filename, flags) ;
		}
		
        ArrayList<String> results = new ArrayList<String>();
        
		FileReader fReader = new FileReader(filename);
        BufferedReader bReader = new BufferedReader (fReader);
        
        String line = null;
        int startIndex, endIndex;
        while ((line = bReader.readLine()) != null)
        {
        	startIndex = line.indexOf(flags[0]);
        	
        	if (startIndex != -1) {
        		startIndex += flags[0].length();
        		endIndex = line.indexOf(flags[1], startIndex);
        		results.add(line.substring(startIndex, endIndex));
        	}
        }
        
        bReader.close();
        fReader.close();

        String[] formattedResults = new String[results.size()];
        for (int i = 0; i < results.size(); i++) {
        	formattedResults[i] = results.get(i);
        }
        
        return formattedResults;
	}
	
	/**
	 * Name:			
	 * Description:		
	 * PreCondition:	
	 * PostCondition:	
	 * Parameters:		
	 * Notes:			
	 */
	private static String[] complexInventory(String filename, String[] flags) throws IOException {
		if (flags.length == 2) {
			return simpleInventory(filename, flags) ;
		}

        ArrayList<String> series = new ArrayList<String>();
        ArrayList<String> results = new ArrayList<String>();
        
		FileReader fReader = new FileReader(filename);
        BufferedReader bReader = new BufferedReader (fReader);
        
        String line = null;
        int startIndex, subStartIndex, subEndIndex;
        while ((line = bReader.readLine()) != null)
        {
        	startIndex = line.indexOf(flags[0]);
        	
        	if (startIndex != -1) {
        		line = bReader.readLine();
        		
        		while((subStartIndex = line.indexOf(flags[1])) != -1) {
        			subStartIndex += flags[1].length();
        			subEndIndex = line.indexOf(flags[2]);
        			
        			series.add(line.substring(subStartIndex, subEndIndex));
        			line = bReader.readLine();
        		}
        		
        		String entry = "";
    			for (String s : series) {
    				entry += s + ", ";
    			}
    			
    			results.add(entry.substring(0, entry.length() - 2));
    			series.clear();
        	}
        }
        
        bReader.close();
        fReader.close();

        String[] formattedResults = new String[results.size()];
        for (int i = 0; i < results.size(); i++) {
        	formattedResults[i] = results.get(i);
        }
        
        return formattedResults;
	}
	/*
	//Test harness
	public static void main(String[] args) {
		XMLHandler tester = new XMLHandler(new String[]{"Catalog.xml"});
		
		Query firstCourseID = new CourseQuery(ACTION.Read, RETURN_TYPE.First, FIELD.ID);
		Query firstCourseName = new CourseQuery(ACTION.Read, RETURN_TYPE.First, FIELD.Name);
		Query firstCourseAvailability = new CourseQuery(ACTION.Read, RETURN_TYPE.First, FIELD.Availability);
		
		Query fullIDQuery = new CourseQuery(ACTION.Read, RETURN_TYPE.All, FIELD.ID);
		Query fullNameQuery = new CourseQuery(ACTION.Read, RETURN_TYPE.All, FIELD.Name);
		Query fullAvailabilityQuery = new CourseQuery(ACTION.Read, RETURN_TYPE.All, FIELD.Availability);
		
		try {
			tester.query(firstCourseID);
			tester.query(firstCourseName);
			tester.query(firstCourseAvailability);
			System.out.println("First Course");
			System.out.println(firstCourseID.review()[0] + ": " + firstCourseName.review()[0]);
			System.out.println("Available: " + firstCourseAvailability.review()[0] + "\n");
			
			tester.query(fullIDQuery);
			tester.query(fullNameQuery);
			tester.query(fullAvailabilityQuery);
			String[] allIDs = fullIDQuery.review();
			String[] allNames = fullNameQuery.review();
			String[] allAvail = fullAvailabilityQuery.review();

			System.out.println("All Courses");
			for (int i = 0; i < allIDs.length; i++) {
				System.out.println(allIDs[i] + ": " + allNames[i]);
				System.out.println("Available: " + allAvail[i] + "\n");
			}
		} catch (IOException e) {
			e.printStackTrace();
		}
	}*/
}
